﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AIOptimization.Algorithms.Repositories;

namespace AIOptimization.Algorithms
{
    public class BacterialForagingOptimization
    {
        Random rd = new Random(0);
        bool parameterSeted = false;
        public int ColonySize { get; set; }
        public int ChemotacticCount { get; set; }
        public int MaxSwimStepCount { get; set; }
        public double ProbabilityDispersal { get; set; }
        public double MovementIncrement { get; set; }
        public int ReproductionStepCount { get; set; }
        public int DispersalStepCount { get; set; }
        public double MinValue { get; set; }
        public double MaxValue { get; set; }
        public int Dimension { get; set; }
        public delegate void NewBestResultEvent(object o, BestBacteriumResult e);
        public event NewBestResultEvent NewBestResult;

        private void OnNewBestResult(BestBacteriumResult e)
        {
            if (NewBestResult != null)
                NewBestResult(this, e);
        }

        public void SetParameter(int colonySize, int chemotacticCount, int maxSwimStepCount, double probabilityDispersal, double movementIncrement, int reproductionStepCount, int dispersalStepCount, double minValue, double maxValue, int dimension = 2)
        {
            parameterSeted = true;
            ColonySize = colonySize;
            ChemotacticCount = chemotacticCount;
            MaxSwimStepCount = maxSwimStepCount;
            ProbabilityDispersal = probabilityDispersal;
            MovementIncrement = movementIncrement;
            ReproductionStepCount = reproductionStepCount;
            DispersalStepCount = dispersalStepCount;
            MinValue = minValue;
            MaxValue = maxValue;
            Dimension = dimension;
        }

        public List<Bacterium> Execute()
        {
            List<Bacterium> colony = new List<Bacterium>();
            for (int i = 0; i < ColonySize; i++)
            {
                Bacterium bacteria = new Bacterium(i, Dimension, MinValue, MaxValue);
                double cost = Cost(bacteria.position);
                bacteria.cost = cost;
                bacteria.prevCost = cost;
                colony.Add(bacteria);
            }

            double bestCost = colony[0].cost;
            int indexOfBest = 0;
            for (int i = 0; i < ColonySize; ++i)
            {
                if (colony[i].cost < bestCost)
                {
                    bestCost = colony[i].cost;
                    indexOfBest = i;
                }
            }
            double[] bestPosition = new double[Dimension];
            colony[indexOfBest].position.CopyTo(bestPosition, 0);
            int t = 0;
            for (int l = 0; l < DispersalStepCount; ++l)
            {
                for (int k = 0; k < ReproductionStepCount; ++k)
                {
                    for (int j = 0; j < ChemotacticCount; ++j)
                    {
                        for (int i = 0; i < ColonySize; ++i)
                        {
                            colony[i].health = 0.0;
                        }

                        for (int i = 0; i < ColonySize; ++i)
                        {
                            double[] tumble = new double[Dimension];
                            for (int p = 0; p < Dimension; ++p) { tumble[p] = 2.0 * rd.NextDouble() - 1.0; }
                            double rootProduct = 0.0;
                            for (int p = 0; p < Dimension; ++p) { rootProduct += (tumble[p] * tumble[p]); }
                            for (int p = 0; p < Dimension; ++p) { colony[i].position[p] += (MovementIncrement * tumble[p]) / rootProduct; }
                            colony[i].prevCost = colony[i].cost;
                            colony[i].cost = Cost(colony[i].position);
                            colony[i].health += colony[i].cost;

                            if (colony[i].cost < bestCost)
                            {
                                bestCost = colony[i].cost;
                                colony[i].position.CopyTo(bestPosition, 0);
                                OnNewBestResult(new BestBacteriumResult(i, t, bestCost, bestPosition));                                
                            }

                            int m = 0;

                            while (m < MaxSwimStepCount && colony[i].cost < colony[i].prevCost)
                            {
                                ++m;
                                for (int p = 0; p < Dimension; ++p) { colony[i].position[p] += (MovementIncrement * tumble[p]) / rootProduct; }
                                colony[i].prevCost = colony[i].cost;
                                colony[i].cost = Cost(colony[i].position);
                                if (colony[i].cost < bestCost)
                                {
                                    bestCost = colony[i].cost;
                                    colony[i].position.CopyTo(bestPosition, 0);
                                    OnNewBestResult(new BestBacteriumResult(i, t, bestCost, bestPosition));                                
                                }
                            }
                        }
                        ++t;
                    }
                    Array.Sort(colony.ToArray());
                    for (int left = 0; left < ColonySize / 2; ++left)
                    {
                        int right = left + ColonySize / 2;
                        colony[left].position.CopyTo(colony[right].position, 0);
                        colony[right].cost = colony[left].cost;
                        colony[right].prevCost = colony[left].prevCost;
                        colony[right].health = colony[left].health;
                    }
                }
                for (int i = 0; i < ColonySize; ++i)
                {
                    double prob = rd.NextDouble();
                    if (prob < ProbabilityDispersal)
                    {
                        for (int p = 0; p < Dimension; ++p)
                        {
                            double x = (MaxValue - MinValue) * rd.NextDouble() + MinValue;
                            colony[i].position[p] = x;
                        }
                        double cost = Cost(colony[i].position);
                        colony[i].cost = cost;
                        colony[i].prevCost = cost;
                        colony[i].health = 0.0;
                        if (colony[i].cost < bestCost)
                        {
                            bestCost = colony[i].cost;
                            colony[i].position.CopyTo(bestPosition, 0);
                            OnNewBestResult(new BestBacteriumResult(i, t, bestCost, bestPosition));                                
                        }
                    }
                }
            }
            ShowVector(bestPosition);
            return colony;
        }

        public string ShowVector(double[] vector)
        {
            StringBuilder s = new StringBuilder();
            s.Append("[ ");
            for (int v = 0; v < vector.Length; ++v)
            {
                s.Append(vector[v].ToString("F4") + " ");
            }
            s.Append("]");
            return s.ToString();
        }

        public virtual double Cost(double[] position)
        {
            //  Rastrigin function. f(x,y) = (x^2 - 10*cos(2*PI*x)) + (y^2 - 10*cos(2*PI*y)) + 20 
            return ObjectiveFunction(position);
            
        }

        public virtual double ObjectiveFunction(double[] X)
        {
            throw new Exception("You must be override this method. This method's your objective function. /r/n For example:f(x) = 3 + (x0^2 + x1^2) /r/n 3.0 + (x[0] * x[0]) + (x[1] * x[1]);");
        }
    }
}
